# 搜索引擎

# 相关概念

  • 词条化(Tokenization)
  • 词项(Term):索引和查询都是以词项为基本单位,词项是词条化的结果
  • 词典 / 字典(Term Dictionary):词条的集合
  • 词项-文档关联矩阵(Incidence Matrix)
  • 倒排索引(Inverted Index),也称反向索引,搜索引擎中最常见的数据结构,将文档中的作为关键字,建立词与文档的映射关系,通过对倒排索引的检索,可以根据词快速获取包含这个词的文档列表
  • 倒排记录表(Posting List):用于记录出现过某个单词的所有文档的文档列表以及单词在该文档中出现的位置信息,每条记录称为一个倒排项,通过倒排列表即可获知哪些文档包含哪些单词
  • 倒排文件(Inverted File):倒排记录表在磁盘中的物理存储文件
  • 段(Segment):索引中最小的独立存储单元,一个索引文件由一个或者多个段组成(在 Lucene 中,段有不变性,段一旦生成,在段上只能读取、不可写入)
  • 分词:将句子或者段落进行切割,从中提取出包含固定语义的词
  • 停止词(Stop Word):没有具体含义、区分度低的词
  • 排序:当输入一个关键字进行搜索时,将相关度更大的内容排在前面

# Lucene (opens new window)

  • 高性能、可伸缩的、开源的信息检索
  • 提供了完整的查询引擎和索引引擎

# Lucene 索引的构建过程

  • 通过指定的数据格式,将 Lucene 的 Document 传递给分词器 Analyzer 对各字段进行分词
  • 经过分词器分词之后,通过索引写入工具 IndexWriter 将得到的索引写入到索引库,Document 本身也会被写入一个文档信息库
  • 分词器不同,建立的索引数据就不同

Lucene索引的构建过程

# Lucene 索引搜索过程

  • 首先构建查询的 Query,通过 IndexSearcher 进行查询,得到命中的 TopDocs
  • 然后通过 TopDocs 的 scoreDocs() 方法,拿到 ScoreDoc,通过 coreDoc,得到对应的文档编号
  • IndexSearcher 通过文档编号,使用 IndexReader 对指定目录下的索引内容进行读取,得到命中的文档后返回

Lucene索引搜索过程

# Lucene 的使用

  • 构建索引、索引更新与删除、条件查询、结果排序、高亮、中文分词器、索引优化、分布式扩展

# Solr

  • 基于 Lucene 开发的全文检索服务器
  • 用户可以通过 HTTP POST 请求,向服务器提交 Document,生成索引,以及进行索引的更新和删除操作
  • 支持添加多种格式的索引,如:HTML、PDF、微软 Office 系列软件格式以及 JSON、XML、CSV 等纯文本格式

# Elasticsearch 简介

  • 基于 Lucene 构建的开源、分布式、RESTful 搜索引擎
  • 优点
    • 分布式:Elasticsearch 横向扩展灵活,扩容时,Elasticsearch 的自动发现机制会识别新增的节点并重新平衡分配数据
    • 全文检索:自带多语言支持、强大的查询语言、地理位置支持、上下文感知的建议、自动完成和搜索片段
    • 近实时搜索(Near Real-Time Search,NTR)和聚合分析
    • 高可用:Elasticsearch 集群会自动发现新的或失败的节点,重组和重新平衡数据
    • 模式自由:动态 mapping 机制可以自动检测数据的结构和类型,创建索引,并使数据可搜索
    • RESTful API:支持使用 JSON 基于 HTTP 请求来进行操作
  • 主要功能:分布式搜索引擎、大数据近实时分析引擎
  • 应用场景:信息检索、NoSQL 数据库、日志分析
  • 与 MongoDB 的区别:在使用场景上,Elasticsearch 适用于全文检索场景,而 MongoDB 适用于数据大批量存储的场景
  • Elasticsearch Guide (opens new window)
  • 版本对比 (opens new window)

# 基本概念

  • 集群(cluster):一个集群由一个唯一的名字标识,称为 cluster name,集群名称默认是 elasticsearch,具有相同集群名称的节点才会组成一个集群

  • 节点(node):集群中的一个服务器,在启动时节点会使用单播查找在同一网段下具有相同集群名并且已存在的集群,并尝试加入它(默认发现模块 Zen Discovery)

  • 索引(index):作为名词时,表示一个拥有相似特征的文档的集合,索引的数据结构是倒排索引,索引名中不能含有大写字母;作为动词时,是指将一个文档存储到索引里

  • 类型(type):索引的一个逻辑上的分类或分区,在 6.0 版本后要求一个索引只能包含一个类型

  • 文档(document):一个可被索引的基础信息单元,一般是 JSON 格式,包含一些 field

    • 一个文档的 _index、_type 和 _id 唯一标识一个文档
    • 文档是索引的基本单位
  • 字段(field):组成文档的最小单位,每个字段由 3 部分组成:名称(name)、类型(type)和取值(value)

  • 映射(mapping):定义一个文档以及其所包含的字段如何被存储和索引

  • 分片(shard)、副本(replica):每个索引可以被分成多个分片,可以有一至多个副本;副本分片不与主分片置于同一节点上;在索引创建之后,可以在任何时候动态地改变副本的数量,但是不能改变分片的数量

    • 分片是把一个大的索引分成多份放到不同的节点上来加速查询效率
    • 每个分片实质是 Lucene 中的一个索引文件,因此会消耗文件句柄、内存和 CPU 资源
    • Elasticsearch 使用的是基于文档的分区方式(每个分区持有整个文档集的一个子集),而非基于词条的分区方式(每个分区拥有一部分词条,词条里面包含了与该词条相关的整个 index 的文档数据)
    • 分片的数量以能够最大化查询效率为原则(最终分片数量 = 数据总量/30 GB):分片过少,数据量很大时,索引文件也会很大,不能发挥分布式搜索的优点,降低整体的查询效率;分片数过多,在分发查询请求、合并搜索结果时会浪费时间
    • 集群中的每个节点默认限制 1000 个分片(max_shards_per_node)
    分片类型 支持处理的请求 数量是否可修改 其他说明
    主分片 支持处理查询和索引请求 在创建索引时设定,设定后不可更改 索引内任意一个文档都存储在一个主分片中,所以主分片的数量和大小决定着索引能够保存的最大数据量
    注意:主分片不是越多越好,因为主分片越多,Elasticsearch 性能开销也会越大。
    副本分片 支持处理查询请求,不支持处理索引请求 可在任何时候添加或删除副本分片 副本分片对搜索性能非常重要,主要体现在以下两个方面:
    1. 提高系统的容错性,当某个节点某个分片损坏或丢失时可以从副本中恢复。
    2. 提高 Elasticsearch 的查询效率,Elasticsearch 会自动对搜索请求进行负载均衡。
  • recovery:代表数据恢复或数据重新分布,Elasticsearch 在有节点加入或退出时会根据机器的负载对索引分片进行重新分配,挂掉的节点重新启动时也会进行数据恢复。

  • gateway:代表 Elasticsearch 索引快照的存储方式,Elasticsearch 默认优先将索引存放到内存中,当内存满时再将这些索引持久化存储至本地硬盘。gateway 对索引快照进行存储,当这个 Elasticsearch 集群关闭再重新启动时就会从 gateway 中读取索引备份数据。Elasticsearch 支持多种类型的 gateway,如本地文件系统(默认)、分布式文件系统、Hadoop 的 HDFS 或 OSS。

  • discovery.zen:代表 Elasticsearch 的自动发现节点机制,Elasticsearch 是一个基于 p2p 的系统,它先通过广播寻找存在的节点,再通过多播协议进行节点之间的通信,同时也支持点对点的交互。

  • Transport:Transport 代表 Elasticsearch 内部节点或集群与客户端的交互方式,默认使用 TCP 协议进行交互。同时,通过插件的方式集成,也支持使用 HTTP 协议(JSON 格式)、thrift、servlet、memcached、zeroMQ 等传输协议进行交互。Elasticsearch 默认的 HTTP 端口是 9200,TCP 端口是 9300(节点间交互或 Java API 中使用)

# 架构设计

Elasticsearch整体架构

# 数据写入过程

  • 数据写入操作是在 Elasticsearch 的内存中执行的,数据会被分配到特定的分片和副本上,最终数据会存储到磁盘上持久化。

# 分段存储

  • 在磁盘上的索引数据以分段形式存储。在索引中,索引文件被拆分为多个子文件,其中每个子文件就叫作段,每个段都是一个倒排索引的小单元。
  • 段具有不变性,一旦索引的数据被写入硬盘,就不能再修改。
  • 当分段被写入磁盘后会生成一个提交点,提交点意味着一个用来记录所有段信息的文件已经生成。
  • 新增数据时,会在当前文档新增一个段。
  • 删除数据时,由于分段不可修改的特性,Elasticsearch 不会把文档从旧的段中移除,而是在每一个提交点都引入一个 .del 文件,.del 文件中会记录这些被删除文档的段信息。被标记删除的文档仍然可以被查询匹配到,但它会在最终结果被返回前通过 .del 文件将其从结果集中移除
  • 当更新数据时,由于分段不可修改的特性,Elasticsearch 无法通过修改旧的段来反映文档的更新,因此,更新操作变成了两个操作的结合,即先删除、后新增。Elasticsearch 会将旧的文档从 .del 文件中标记删除,然后将文档的新版本索引到一个新的段中。在查询数据时,两个版本的文档都会被一个查询匹配到,但被删除的旧版本文档在结果集返回前就会被移除

# 延迟写策略

  • 在 Elasticsearch 中,索引写入磁盘的过程是异步的。

    • 每当有新的数据写入时,就将其先写入 JVM 的内存中。当达到默认的时间或者内存的数据达到一定量时,会触发一次刷新(Refresh)操作,将内存中的数据生成一个新的分段并写入文件系统缓存,稍后再被系统 fsync 到磁盘中,并生成提交点。
    • 由于新的数据会继续写入内存,而内存中的数据并不是以段的形式存储的,因此不能提供检索功能
    • 只有当数据经由内存刷新到文件系统缓存,并生成新的段后,新的段才能供搜索使用,而不需要等到被刷新到磁盘才可以被搜索。
    • 写入和打开一个新段的过程叫作刷新(Refresh)。在默认情况下,每个分片会每秒自动刷新一次(近实时搜索),也可以手动触发刷新或者在创建索引时设置刷新频率。
  • 事务日志(Translog)机制:用于记录所有还没有持久化到磁盘的数据。

    • 新文档被索引之后,先被写入内存中。为了防止数据丢失,Elasticsearch 会追加一份数据到事务日志中
    • 新的文档持续在被写入内存时,同时也会记录到事务日志中。
    • 随着新文档索引不断被写入,当日志数据大小超过某个值(如 512MB),或者超过一定时间(如 30 min)时,Elasticsearch 会触发一次 Flush:先执行 Refresh 操作,然后清空日志文件。

# 段合并

  • 自动刷新时,默认每秒都会创建一个新的段。
  • 由于搜索请求要检查到每个段,然后合并查询结果,因此段越多,搜索速度越慢。
  • 在段合并过程中,Elasticsearch 会将那些旧的已删除文档从文件系统中清除,被删除的文档不会被拷贝到新的大段中。
  • 在合并过程中,Elasticsearch 会选择一小部分大小相似的段,在后台将它们合并到更大的段中,合并过程不会中断索引和搜索。
  • 在合并结束后,老的段会被删除,新的段被 Flush 到磁盘,同时写入一个包含新段且排除旧的和较小的段的新提交点。打开新的段之后,可以用来搜索。
  • 由于段合并的计算量较大,对磁盘 I/O 的消耗也较大,因此段合并会影响正常的数据写入速率,因此 Elasticsearch 在默认情况下会对合并流程进行资源限制。

# 搜索过程

# 对已知文档的搜索

  • 客户端给主节点 A 发送文档的 Get 请求,此时主节点 A 就成为协同节点
  • 协同节点使用路由算法算出文档所在的主分片,随后将请求转发给主分片所在的节点 B,也可以基于轮询算法在所有副本分片中循环地进行转发
  • 节点 B 在本地分片进行搜索,并将目标文档信息作为结果返给协同节点

# 对未知文档的搜索

  • 搜索过程分为查询阶段(Query Phase)和获取阶段(Fetch Phase)
  • 在查询阶段,查询请求会被广播到索引中的每一个主分片和副本分片中,每一个分片都会在本地执行检索,并在本地各建立一个优先级队列(Priority Queue)。该优先级队列是一份根据文档相关度指标进行排序的列表,列表的长度由 from 和 size 两个分页参数决定。查询阶段可细分成 3 个小的子阶段:
    • 客户端发送一个检索请求给某节点 A协调节点,coordinating node),此时节点 A 会创建一个空的优先级队列,并配置好分页参数 from 与 size
    • 节点 A 将搜索请求发送给该索引中的每一个分片,每个分片在本地执行检索,并将结果添加到本地优先级队列中
    • 每个分片将本地优先级序列中所记录的 IDsort 值发送给节点 A,节点 A 将这些值合并到自己的本地的优先级队列中,并做出全局的排序
  • 在获取阶段,主要是基于上一阶段找到所要搜索文档数据的具体位置,将文档数据内容取回并返回给客户端

# 对词条的搜索

  • 为了能快速找到某个词条,Elasticsearch 对所有的词条都进行了排序,排序词条的集合称为 Term Dictionary,使用二分法查找词条,其查找效率为 O(logn)
  • Term Index,类似字典中的索引页,其中的内容如字母 A 开头的有哪些词条,这些词条分别在哪页(Term Index 存放在内存中)
  • 当查找某个词条时,通过 Term Index,快速地定位到 Term Dictionary 的某个 OffSet(位置偏移),然后从这个位置再往后顺序查找
  • 多词条“联合查询”,方法:
    1. 利用跳表快速做“与”运算:用最短的文档 ID 列表中的 ID 逐个在其他文档 ID 列表中进行查找,都能找到的 ID 即为多个词条的交集结果
    2. 利用 BitSet(位图)按位“与”运算:将词条的文档 ID 列表转化为位图后,将多个词条对应的位图取“与”运算,即可得到交集结果

# 倒排索引的压缩

  • Elasticsearch 对词条对应的文档 ID 列表进行压缩存储:

    • 通过增量编码,将大数变小数,仅存储增量值

    • 按字节存储:对增量编码后的文档ID列表进行分组,再分别计算每一个存储需要最小的字节数

# 工具

# 配置调整

  • 使用 elasticsearh 根目录下 jdk 目录为 Java 路径:修改 elasticsearch-env.bat 或 elasticsearch-env,设置 ES_JAVA_HOME,set ES_JAVA_HOME="%ES_HOME%\jdk"

  • 显式禁用安全认证:在 elasticsearch.yml 配置 xpack.security.enabled: false

  • 允许跨域请求访问:http.cors.enabled: truehttp.cors.allow-origin: "*"

  • 自定义 JVM 参数 (opens new window):将自定义的 JVM 参数文件(以 ".options" 作为后缀)放到 config/jvm.options.d/ 目录下,文件名按字典序加载,eg: a.options > b.options,建议:-Xmx 和 -Xms 设置成一样,-Xmx 不要超过机器内存的 50%,且不要超过 30GB (opens new window)

  • 默认情况下,Elasticsearch 不支持外网访问,如果需要设置外网访问,修改 config/elasticsearch.yml 文件,添加 network.host: 0.0.0.0,监听在本机的所有 IP 地址

  • Elasticsearch 不支持以 root 用户运行

    # 新建用户组
    groupadd esgroup
    # 新增用户
    useradd esroot -g esgroup -p admin
    # 修改文件的拥有者
    chown -R esroot:esgroup /usr/local/soft/elasticsearch-7.10.0
    
    1
    2
    3
    4
    5
    6
  • 启动:bin/elasticsearch -E node.name=node1 -E cluster.name=elasticsearch -E path.data=node1_data -d

# 分词

  • 文本分析(Text Analysis)是把全文本转换一系列单词(term / token)的过程,也称为分词
  • 分析器(Analyzer)由三部分组成:
    • 字符过滤器(character filter),分词之前的预处理,过滤 HTML 标签、特殊符号转换等
    • 分词器(tokenizer),用于分词
    • Token 过滤器(token filter),用于标准化输出,即将切分的单词进行加工,例如:大小写转换,去除词条等

# 内置的分析器 (opens new window)

  • Standard Analyzer:标准分析器(默认),将词汇单元转换成小写形式,并且去除了停用词和标点符号,支持中文(采用的方法为单字切分),停用词指语气助词等修饰性词语,如 the、an、的、这等
  • Simple Analyzer:简单分析器,首先通过非字母字符分割文本信息,并去除数字类型的字符,然后将词汇单元统一为小写形式
  • Whitespace Analyzer:空格分析器,仅去除空格,不会将字符转换成小写形式,不支持中文,不对生成的词汇单元进行其他标准化处理
  • Stop Analyzer:与简单分析器相比,增加了去除停用词的处理
  • Keyword Analyzer:关键词分析器,不进行分词,而是直接将输入作为一个单词输出
  • Pattern Analyzer:通过正则表达式自定义分隔符,默认是 ”\W+",即把非字词的符号作为分隔符
  • Language Analyzers:特定语言的分析器,支持 English、French、Spanish 等语言,不支持中文
  • Fingerprint Analyzer:指纹分析仪分析器,通过创建标记进行重复检测

# 自定义分析器 (opens new window)

PUT my-index-000001
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "my_tokenizer",
          "filter": ["lowercase"]
        }
      },
      "tokenizer": {
        "my_tokenizer": {
          "type": "ngram",
          "min_gram": 1,
          "max_gram": 3,
          "token_chars": ["letter", "digit"]
        }
      }
    }
  }
}

POST my-index-000001/_analyze
{
  "analyzer": "my_analyzer",
  "text": "2 Quick Foxes."
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 中文分析器

IK、Ansj (opens new window)Jcseg (opens new window)、庖丁分词、jieba (opens new window)、sego、THULAC (opens new window)

# IK Analysis

  • 对应插件:elasticsearch-analysis-ik (opens new window)

  • 本地安装:./bin/elasticsearch-plugin install file:///path/to/elasticsearch-analysis-ik-xxx.zip

    • elasticseach-plugin 文件需要 URL 协议
    • 配置文件在 {conf}/analysis-ik/config/IKAnalyzer.cfg.xml
  • IK Analysis 中有两种分词策略:ik_smart(粗粒度分词)、ik_max_word(细粒度分词),细粒度分词会对一个词重复分词,而粗粒度分词不会

GET /_analyze
{
  "text":"中华人民共和国人民大会堂",
  "analyzer":"ik_smart"
}
1
2
3
4
5

# 插件 (opens new window)

# ICU Analysis Plugin (opens new window)

elasticsearch-analysis-icu

# 基本操作

# 索引管理

# Index Settings (opens new window)

  • 索引的 settings 分为静态和动态两种。静态的 setting 只能在创建索引时或在关闭的索引上设置;动态的 setting 则可以使用 update-index-settings API 来实时设置。

  • 常见的静态索引设置

    • number_of_shards:索引主分片数,只能在创建索引时指定,后期无法修改(也不能在关闭的索引上更改),7.x 版本之后(包含 7.0)默认值为 1,最大可以设置为 1024
    • number_of_routing_shards
    • mapping.ignore_malformed:是否允许在所有映射类型中全局忽略格式不正确的数据,默认为 false
  • 常见的动态索引设置

    • number_of_replicas:每个主分片的副本数,默认为 1
    • auto_expand_replicas:基于数据节点可以自动扩展的副本数,默认为 false
    • search.idle.after:分片被认为搜索空闲之前没有收到请求或搜索的时间,默认为 30s
    • refresh_interval:执行刷新操作的频率,默认为 1 秒,设置为 -1 表示禁用自动刷新。如果没有显式设置,分片在收到搜索请求前至少 search.idle.after 秒内不会后台刷新。
    • max_result_window:搜索该索引的 from + size 的最大值。默认为 10000。搜索请求占用的堆内存和时间与 from + size 成正比,这限制了内存。请参阅 Scroll (opens new window)Search After (opens new window) 以获得更有效的替代方法。

# 创建索引

PUT 索引名
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 1,
    "mapping.ignore_malformed": false
  }
}
1
2
3
4
5
6
7
8

注意:索引名不能含大写字母

# 修改副本

PUT 索引名/_settings
{
  "number_of_replicas": 3
}
1
2
3
4

# 删除索引

DELETE 索引名

action.destructive_requires_name 默认为 true,删除或关闭时索引名称不允许使用通配符或 _all

# 查看索引

GET 索引名/_settings

GET _all/_settings

# 打开或关闭索引

  • 索引可以进行打开和关闭操作
  • 一个关闭了的索引几乎不占用集群资源(除了维持本身的元信息),但仍会继续占用磁盘空间
  • 已关闭的索引只能查看 index 的配置信息,不能进行读写操作
  • 已关闭的索引可以重新开启,将通过常规的恢复过程开启

POST 索引名/_close?ignore_unavailable=true

POST 索引名/_open

# 别名 (opens new window)

  • 一组数据流或索引的辅助名称
  • 类型:一个数据流别名指向一个或多个数据流;一个索引别名指向一个或多个索引
  • 如果别名指向多个索引或数据流并且未设置 is_write_index,使用别名的写入请求会被拒绝
  • 使用路由选项将别名请求路由到特定分片:routing、index_routing、search_routing
  • 使用别名时,需要将别名指向的索引的分片控制在 1024 个以内
# 增加别名、移除别名
POST _aliases
{
  "actions": [
    { "add": { "index": ["test1", "test2"], "alias": "test" } },
    { "add": { "index": "logs-*", "alias": "logs" } },
    { "add": { "index": ["user1"], "alias": "users" }, "filter": {"term": {"country": "china"}} },
    { "remove": { "index": "test3", "alias": "test" } }
  ]
}

GET _alias

GET test1/_alias

# 使用索引模板在创建索引时添加索引别名
{
  "template": {
    "aliases": {
      "my-alias": {}
    }
  }
}

# 添加字段别名
PUT trips
{
  "mappings": {
    "properties": {
      "distance": {
        "type": "long"
      },
      "route_length_miles": {
        "type": "alias",
        "path": "distance" 
      },
      "transit_mode": {
        "type": "keyword"
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# 索引模板 (opens new window)

  • 索引模板只在创建索引时应用,修改模板不会对现有索引产生影响

  • 可以设定多个索引模板,这些索引模板的设置将被 merge 在一起

  • 可以通过对索引模板指定 order 的数值,来控制 merge 的过程,数值越大,优先级越高

  • 创建或更新索引模板

    PUT _template/模板名称
    {
      "index_patterns": ["test*", "bar*"],
      "order": 1,
      "settings": {
        "number_of_shards": 1,
        "number_of_replicas": 2
      },
      "mappings": {
        "date_detection": true,
        "numeric_detection": false,
        "dynamic_templates": [],
        "properties": {}
      },
      "aliases": {
        "mydata": { }
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
  • 查看模板:GET _template/<模板名称>

  • 删除模板:DELETE _template/模板名称

# 映射 (opens new window)

# 查看映射

GET 索引名/_mapping

# 字段类型 (opens new window)

Elasticsearch字段类型
  • text:字段内容会被分析,在生成倒排索引以前,字符串会被分词器分成一个一个词项,不用于排序,很少用于聚合
  • keyword:用于结构化内容,例如 ID、email、状态码等,常用于过滤、排序、聚合,只能通过精确值搜索到
  • constant_keyword
  • wildcard
  • 数字类型:byte、short、integer、long、float、double、half_float、scaled_float(带有缩放因子的缩放类型浮点数)
    • 对于数字类型的字段,在满足需求的情况下,要尽可能选择范围小的数据类型。(字段的长度越短,索引和搜索的效率越高)
    • 处理浮点数时,优先考虑使用 scaled_float 类型
    • scaled_float 表示通过缩放因子 scaling_factor 把浮点数变成 long 类型,即存储的是原始值乘以缩放因子并四舍五入得到的新值。ES 底层存储的是整数类型,因为压缩整数比压缩浮点数更加节省存储空间。
  • date:格式化日期的字符串、代表 milliseconds-since-the-epoch 的长整型数、代表 seconds-since-the-epoch 的整型数
    • 如果没有设置 date 的 format,默认为 strict_date_optional_time||epoch_millis
    • 内部会把日期转换为 UTC,并将其存储为表示 milliseconds-since-the-epoch 的长整型数
  • boolean:只接受 true、false、"true"、"false"
  • binary:接受 Base64 编码的字符串,默认不存储(store 属性值为 false),也不可搜索
  • array:默认情况下任何字段都可以包含一个或者多个值,但是一个数组中的值必须是同一种类型
    • 数组可以包含 null 值,空数组 [] 会被当作 missing field 对待
    • array 类型不需要提前做任何配置,默认支持
  • object:用于对象,写入到 Elasticsearch 之后,文档会被索引成简单的扁平 key-value 对
  • nested:用于对象数组,将数组中每个对象作为单独的隐藏文档来索引
  • geo_point:用于存储地理位置信息的经纬度
  • geo_shape:用于存储一块区域,比如矩形、三角形或者其他多边形
  • ip:用于存储 IPv4 或者 IPv6 的地址
  • 范围类型:integer_range、float_range、long_range、double_range、date_range
  • token_count:用于统计字符串分词后的词项个数(本质上是一个整数型字段)
  • alias:别名
  • flattened (opens new window)

# 元字段

  • 描述文档本身的字段
元字段分类 具体属性 作用
文档属性的元字段 _index 文档所属索引
_type 文档的类型
_id 文档 id
源文档的元字段 _source 文档的原始 JSON 字符串
_size _source 字段的大小,默认不支持,需安装 mapper-size 插件
_doc_count A custom field used for storing doc counts when a document represents pre-aggregated data.
索引的元字段 _field_names 文档中包含非空值的所有字段
_ignored 文档在索引时由于 ignore_malformed 被忽略的所有字段
路由的元字段 _routing 将文档路由到特定分片的自定义路由值
其它元字段 _meta 应用程序自定义的元数据
_tier The current data tier preference of the index to which the document belongs.
  • 已废除的元字段:_uid、_all、_parent

# 动态映射

# 动态字段映射

JSON 格式数据 "dynamic":"true" "dynamic":"runtime"
null 没有字段被添加 没有字段被添加
true 或 fasle boolean boolean
浮点类型数字 float double
整数 long long
JSON 对象 object 没有字段被添加
数组 由数组中第一个非空值决定 由数组中第一个非空值决定
日期检测通过的字符串 date date
数值检测通过的字符串 float 或 long double 或 long
没有通过日期检测、数值检测的字符串 带有 .keyword 子字段的 text keyword
  • date_detection,是否开启日期检测,默认 true
  • dynamic_date_formats,指定日期检测时使用的格式,默认 [ "strict_date_optional_time","yyyy/MM/dd HH:mm:ss Z||yyyy/MM/dd Z"]
  • numeric_detection,是否开启数值检测,默认 false

# 动态模板 (opens new window)

# 增加一个名为 longs_as_strings 的映射模板,如果字段名称以 long_ 开头且不是以 _text 结尾,字符串转为 long 类型
PUT my_index
{
  "mappings": {
    "dynamic_templates": [
      {
        "longs_as_strings": {
          "match_mapping_type": "string",
          "match": "long_*",
          "unmatch": "*_text",
          "mapping": {
            "type": "long"
          }
        }
      }
    ]
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 显式映射

  • 在创建索引时手工指定索引映射
  • 可以向现有映射添加字段,但无法更改现有字段的映射或字段类型。If you need to change the mapping of a field in other indices, create a new index with the correct mapping and reindex (opens new window) your data into that index.
PUT 索引名/_mapping
{
  "mappings": {
    "properties": {
      字段名: {
        "type": 数据类型,
        "analyzer": 分词器类型,
        "search_analyzer": 分词器类型,
        ...
      },
      ...
    }
  }
}

# 向现有映射添加字段
PUT /my_index/_mapping
{
  "properties": {
    "employee_id": {
      "type": "keyword",
      "index": false
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 映射参数

  • analyzer:用于指定文本字段的分词器,对索引和查询都有效
  • search_analyzer,默认情况下查询会使用 analyzer 属性指定的分词器,但也可以被 search_analyzer 覆盖
  • format:用于指定日期格式,默认为 strict_date_optional_time||epoch_millis
  • ignore_above:用于指定字段分词和索引的字符串最大长度,超过最大值的会被忽略,只适用于 keyword 类型
  • normalizer:用于解析前的标准化配置,比如把所有的字符转化为小写
  • boost:用于设置字段的权重,推荐在查询时才指定 boost
  • ignore_malformed:默认 false,此时给一个字段索引不合适的数据类型会发生异常,进而导致整个文档索引失败
  • coerce:默认 true,用来清除脏数据,例如:字符串将被强制转换为数字,浮点数将被截断转换为整数值
  • copy_to:用于把多个字段的值复制到一个组字段中,不支持复制对象类型的字段值
  • dynamic:用于控制是否自动新增字段,参数值:
    • true:自动添加字段(默认)
    • runtime:新字段作为运行时字段添加到映射中,新字段不可索引,在查询时可从 _source 中获取
    • false:忽略新的字段
    • strict:严格模式,发现新的字段抛出异常
  • enabled:用于控制是否索引该字段,设为 false 的字段,只能从 _source 中获取,不可以被搜索(只能应用于顶级映射定义和对象类型字段)
  • index:指定字段是否索引,不索引也就不可搜索,默认 true
  • store:指定字段是否存储,默认 false,在查询时可使用 stored_fields 参数获取指定存储字段的值
  • index_options:控制索引时存储哪些信息到倒排索引中
  • term_vector:词向量(关于词的一些统计信息的统称)
  • fields:可以让同一字段有多种不同的索引方式。比如一个文本类型的字段,可以使用 text 类型做全文检索,使用 keyword 类型做聚合和排序,"fields": {"raw": {"type": "keyword"}}
  • doc_values:为加快排序、聚合操作,在建立倒排索引时,额外增加一个列式存储映射默认开启,text 类型不支持 doc_values,在查询时可使用 docvalue_fields 参数获取指定字段的值
  • fielddata:默认 false,开启后,text 字段在查询时会生成一个 fielddata 的数据结构,fielddata 在字段首次被聚合、排序或者使用脚本的时候生成
  • norms:用于标准化文档,以便查询时计算文档的相关性,默认 true
  • null_value:让值为 null 的字段显式地可索引、可搜索
  • position_increment_gap:指定词项的间距,默认 100
  • properties
  • similarity:用于指定字段的评分算法或相似度,BM25(默认)、classic(已被废弃,TF/IDF 算法)、boolean

注意:字段类型为 text 时才能指定 analyzer、search_analyzer

# 映射限制

  • index.mapping.total_fields.limit:索引中的最大字段数,默认 1000
  • index.mapping.depth.limit:设置一个字段的最大深度,表示字段可以包含的内部对象的数量,默认 20
  • index.mapping.nested_fields.limit:设置一个索引中嵌套字段的最大数量,默认 50
  • index.mapping.nested_objects.limit

# 文档管理 (opens new window)

# 添加或替换文档

PUT 索引名/_doc/文档 ID
POST 索引名/_doc/
PUT 索引名/_create/文档 ID
POST 索引名/_create/文档 ID
{
  field1: value1,
  field2: value2,
  ...
}
1
2
3
4
5
6
7
8
9
  • 在默认情况下,接收到新文档后,如果没有对应索引,Elasticsearch 会自动创建索引,action.auto_create_index (opens new window)
  • 指定文档 ID 时,如果索引中已经存在,则执行更新操作,否则执行添加
  • 不指定文档 ID 时,Elasticsearch 会自动生成一个字符串作为文档 ID(此时只能使用 POST 方法)

# 更新文档

  • 在 Elasticsearch 中文档是不可改变的,更新操作的本质是删除旧文档然后对新文档进行索引

  • query-params

    • refresh:可选值 false(默认,不执行任何刷新操作)、true(立即刷新受影响的分片以使此操作对搜索可见)、wait_for(等待刷新以使此操作对搜索可见)
    • retry_on_conflict:指定发生冲突时应重试操作的次数,默认为 0
    • routing:用于将操作路由到特定分片的自定义值
    • timeout:等待分片可用的超时时间,默认为 1m(一分钟)
    • wait_for_active_shards:在继续操作之前必须处于活动状态的分片副本数,默认为 1(主分片)
    • if_seq_no
    • if_primary_term
  • 注意:如果同时指定了 doc 和 script,会报错;当文档不存在且没有设置 upsert 或 doc_as_upsert 时,会提示文档不存在

  • script 中可以通过 ctx 访问以下变量:_source、_index、_type、_id、_version、_routing 和 _now(当前时间戳)

# 更新文档部分字段
# 使用 doc 传递文档片段(Partial Document),doc 包含完整文档的一部分字段,Elasticsearch 对已经存在的文档进行归并(Merge)更新
POST my_index/_update/1
{
  "doc": {
    "name": "new_name"
  }
}
1
2
3
4
5
6
7
8
# 修改字段值
# upsert:如果文档存在,则执行 script 或使用 doc 更新文档部分字段;如果文档不存在,将 upsert 中的内容将作为新文档插入

POST my_index/_update/1
{
  "script": {
    "source": "ctx._source.counter += params.count",
    "lang": "painless",
    "params": {
      "count": 4
    }
  },
  "upsert": {
    "counter": 1
  }
}

# scripted_upsert:为 true 时,无论文档是否存在都会执行脚本
# 如果 scripted_upsert 设置为 true,且同时设置了 upsert,当文档不存在时,会先执行 upsert 中的内容,然后执行脚本
{
  "scripted_upsert": true,
  "script": {
    "source": "ctx._source.counter += params.count",
    "lang": "painless",
    "params": {
      "count": 4
    }
  },
  "upsert": {
    "counter": 1
  }
}

{
  "doc": {
    "name": "new_name"
  },
  "upsert": {
    "counter": 10
  }
}

# doc_as_upsert:为 true 时,使用 doc 的内容作为 upsert 值
{
  "doc": {
    "name": "new_name"
  },
  "doc_as_upsert" : true
}

# upsert 永远不会被执行,不管文档是否不存在,始终执行的是 doc 的内容
{
  "doc" : {
      "name" : "new_name"
  },
  "doc_as_upsert" : true,
  "upsert" : {
      "counter" : 10
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 删除字段值中的某个元素
POST my_index/_update/1
{
  "script": {
    "source": "if (ctx._source.tags.contains(params.tag)) { ctx._source.tags.remove(ctx._source.tags.indexOf(params.tag)) }",
    "lang": "painless",
    "params": {
      "tag": "blue"
    }
  }
}

# 往 tags 列表里添加一个 tag(注意,如果 tag 存在,仍会添加,因为它是一个 list)
"script": {
    "source": "ctx._source.tags.add(params.tag)",
    "lang": "painless",
    "params": {
        "tag": "blue"
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 添加新字段
{
  "script" : "ctx._source.new_field = 'value_of_new_field'"
}

# 移除字段
{
  "script" : "ctx._source.remove('new_field')"
}

# 修改字段名称
{
  "script": {
    "lang": "painless", 
    "source": """
      for (item in params.updateFields){
        if (ctx._source.containsKey(item)){
          def newItem = "df_" + item;
          ctx['_source'][newItem] = ctx._source.remove(item);
        }
      }
    """,
    "params": {
      "updateFields": ["f1", "f2"]
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 如果标签字段包含 green,将删除 doc,否则它不执行任何操作(即该操作会被忽略,返回 noop)
{
  "script": {
    "source": "if (ctx._source.tags.contains(params.tag)) { ctx.op = 'delete' } else { ctx.op = 'none' }",
    "lang": "painless",
    "params": {
      "tag": "green"
    }
  }
}
1
2
3
4
5
6
7
8
9
10
# 查询更新
# https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update-by-query.html
POST my_index/_update_by_query
{
  "script": {
    "source": "ctx._source.count++",
    "lang": "painless"
  },
  "query": {
    "term": {
      "user.id": "kimchy"
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 删除文档

  • DELETE 索引名/_doc/文档 ID

  • 删除一个文档不会立即从磁盘上移除,只是标记该文档的状态为删除;Elasticsearch 会在之后添加更多索引时才会在后台进行删除内容的清理

# 查询删除
POST my_index/_delete_by_query
{
  "query": {
    "match": {
      "user.id": "elkbee"
    }
  }
}
1
2
3
4
5
6
7
8
9

# 复制文档

从一个或更多的索引(源索引)中复制相关的文档到一个新的索引(目标索引)中进行索引重建,目标索引不会复制源索引中的配置信息

POST _reindex
{
  "source": {
    "index": "my_index"
  },
  "dest": {
    "index": "my-new-index"
  },
  "script": {
    "source": "ctx._source.tag = ctx._source.remove(\"flag\")"
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 获取文档

  • 根据 ID 查询:GET 索引名/_doc/文档 ID

  • 检查文档是否存在:HEAD 索引名/_doc/文档 ID,如果文档存在,返回“200-0K”,反之返回“404 - NotFound”

  • 获取文档字段的词项信息和统计信息:GET 索引名/_termvectors/文档 ID?fields=字段名

  • 批量查询

    GET 索引名/_mget
    {
      "docs": [
        {"_id": "1"},
        ...
      ]
    }
    
    # {
    #   "ids": ["1", "2"]
    # }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    GET _mget
    {
      "docs": [
        {
          "_index": "索引名",
          "_id": 1
        }
      ]
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

# 批量操作

# POST 索引名/_bulk
POST _bulk
action_and_meta_data
optional_source
...
1
2
3
4
5
  • 每一行的结尾处都必须有换行字符”\n”,最后一行也要有,行里不能包含非转义字符
  • action_and_meta_data 行指定了将要在哪个文档中执行什么操作,其中 action 必须是 index、create、update、delete,metadata 指明需要被操作文档的 _index、_type、_id
  • index 和 create 的区别:如果文档已存在,create 会创建失败,index 不会
  • delete 请求不需要下一行的 source
  • 由于请求需要被加载到接受请求节点的内存里,整个批量请求越大,给其它请求可用的内存就越小,因此一个批量请求最好保持在 5~15MB 之间(约 1000~5000 个文档,取决于文档的大小)
POST _bulk
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "create" : { "_index" : "test", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }
{ "delete" : { "_index" : "test", "_id" : "2" } }
1
2
3
4
5
6
7
8

# 版本控制

  • 乐观锁机制:每个文档都有一个 version(版本号),当文档被修改时版本号递增
  • 文档的版本号的取值范围为 1 到 2^63^-1
  • Elasticsearch 的文档版本控制机制:
    • 内部版本控制,要求每次操作请求,只有当版本号相等时才能操作成功,默认情况下使用内部版本控制
    • 外部版本控制(版本号设置为外部值,例如在数据库中维护),要求外部文档版本比内部文档版本高时才能更新成功,如 ?version=5&version_type=external

# 路由机制

  • 分片位置计算方法:shard = hash(routing) % number_of_primary_shards
  • Elasticsearch 默认将文档的 _id 或 _parent 作为 routing
  • 指定 routing:?routing=user123

# 刷新控制

  • ?refresh
  • 创建、更新、删除和批量 API 支持设置刷新以控制此请求所做的更改何时对搜索可见
  • 允许值:true、wait_for、false 或 空字符串,默认 fasle

# 高级查询 (opens new window)

  • Elasticsearch 基于 JSON 提供完整的查询 DSL(Domain Specific Language,领域特定语言)来定义查询

  • GET 索引名/_searchPOST 索引名/_search

  • 返回结果中包含以下字段:

    • took:耗时
    • _shards.total:分片总数
    • hits.total:符合查询条件的文档数(搜索请求默认返回的总文档数最多为 10000,如果大于该值,Elasticsearch 只返回 10000 个文档,可通过在查询时设置 track_total_hits (opens new window) = true 准确计算总命中数)
    • hits.max_score:最大匹配度
    • hits.hits:查询到的结果
    • hits.hits._score:匹配度
  • 查询所有:GET 索引名/_doc/_search?q=*

  • 指定返回文档的字段{"_source": [field1, field2]}

  • 返回查询到的文档的版本号:{"version": true}

  • 设置最小评分:{"min_score": 0.6}

  • 分页查询:{"from": 0, "size": 10}

    • from 指定返回结果的开始位置,默认 0
    • size 指定一次返回结果所包含的最大文档数量,默认 10
    • 深度分页(超过 10000)时建议使用带有时间点(PIT)的 search_after (opens new window) 参数

# 匹配所有

  • match_all

    {
      "query": {
        "match_all": {}
      }  
    }
    
    1
    2
    3
    4
    5
  • match_none

# 全文查询

在执行查询之前将每个字段的分词器(或搜索分词器)应用于查询字符串,通常用于在全文字段上

  • match:match 查询会对查询语句进行分词,分词后查询语句中的任何一个词项被匹配,文档就会被搜索到;如果需要查询匹配所有关键词的文档,可以用 and 操作符连接

    {
      "query":{
        "match": {
          "title": {"query": "java 编程思想", "operator": "and"}
        }
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
  • match_phrase:会把 query 内容分词,同时文档还要满足两个条件才会被搜索到:1. 分词后所有词项都要出现在该字段中;2. 字段中的词项顺序要一致。

    • slop 参数用于设置词汇之间是否允许或允许多少个无关词汇的存在,默认值为 0
  • match_phrase_prefix:和 match_phrase 类似,但支持对最后一个词项进行前缀匹配

    • max_expansions 参数可以控制最后一个词项的模糊匹配数,默认值为 50
  • multi_match:用于搜索多个字段,只要其中一个字段满足条件就能查询出来

    {
      "query": {
        "multi_match": {
          "query": value,
          "fields": [field1, field2, ...]
        }
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
  • common terms

  • query_string:允许在一个查询语句中使用多个特殊条件关键字(如:AND|OR|NOT)对多个字段进行查询

  • simple_query_string:接受 Lucene 查询语法,解析过程中发生错误不会抛出异常

# 词项搜索

词项搜索时对倒排索引中存储的词项进行精确操作,通常用于结构化数据,如数字、日期和枚举类型

  • term:用来查找指定字段中包含给定单词的文档,term 查询的查询词不被解析,查的是词项,只有查询词和文档中的词项精确匹配才会被搜索到

    由于倒排索引表自身的特性,整个字段是否相等会难以计算。如果确定某个特定文档是否只包含想要查找的词,首先需要在倒排索引中找到相关的记录并获取文档 ID,然后再扫描倒排索引中的每行记录,查看它们是否包含其他的 terms。这样不仅低效,而且代价高昂。因此,term 和 terms 是必须包含(must contain)操作,而不是必须精确相等(must equal exactly)。

  • terms:用来查询文档中包含多个词的文档,只要指定字段中包含其中一个字段满足条件就能查询出来

    {
      "query": {
        "terms": {
          "title": ["java", "python"]
        }
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
  • range:查询用于匹配在某一范围内的数值型、日期类型或者字符串型字段的文档,只能查询一个字段,比较规则有 gt、gte、lt、lte

  • exists:返回字段中至少有一个非空值的文档

  • prefix:用于查询某个字段中以给定前缀开始的文档

  • wildcard:通配符查询,? 用来匹配一个任意字符,* 用来匹配零个或者多个字符

  • regexp:正则表达式查询

  • fuzzy:通过计算词项与文档的编辑距离(Levenshtein 距离)来得到结果

  • type:查询具有指定类型的文档

  • ids:查询具有指定 id 的文档

# 复合查询

  • bool:把任意多个简单查询组合在一起,使用 must、should、must_not、filter 选项来表示简单查询之间的逻辑,每个选项都可以出现 0 次到多次

    • must:文档必须匹配 must 选项下的查询条件,相当于逻辑运算的 AND
    • should:文档可以匹配 should 选项下的查询条件也可以不匹配,相当于逻辑运算的 OR
    • must_not:与 must 相反,匹配该选项下的查询条件的文档不会被返回
    • filter:和 must —样,匹配 filter 选项下的查询条件的文档才会被返回,但是 filter 不评分,只起到过滤功能,查询效率高,有缓存
    • minimum_should_match:指定返回文档必须匹配的 should 选项的数量或百分比,如果 bool 查询包含至少一个 should 选项且没有 must 或 filter 选项,则默认值为 1,否则默认值为 0
    # 查询 title 中包含关键词 java,并且 price 不能高于 70, description 可以包含也可以不包含虚拟机的书籍
    {
      "query": {
        "bool": {
          "minimum_should_match": 1,
          "must": [
            {"match": {"title": "java"}}
          ],
          "should": [
            {"match": {"description": "虚拟机"}}
          ],
          "must_not": [
            {"range": {"privce": {"gte": 70}}}
          ]
        }
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
  • constant_score:返回具有指定评分的文档

  • dis_max

  • function_score:可以修改查询的文档得分

  • boosting:对两个查询的评分进行调整

# 嵌套查询

  • 查找 nested 类型的字段索引的对象
  • has_child、has_parent、parent_id

# 位置查询

  • geo_distance:查找在一个中心点指定范围内的地理点文档
  • geo_bounding_box:查找落入指定的矩形内的地理坐标
  • geo_polygon:查找在指定多边形内的地理点
  • 查询 geo_shape 类型的地理数据

# 特殊查询

  • more_like_this:查询和提供文本类似的文档,通常用于近似文本的推荐等
  • 使用脚本进行查询
  • percolate

# 搜索高亮

{
  "query": { ... },
  "highlight": {
    "fields": {
      field1: {"type": "plain"},
      field2: {"type": "plain"},
      ...
    },
    "pre_tags": 开始标签,
    "post_tags": 结束标签
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
  • highlight:表示高亮显示,需要在 fields 中配置哪些字段中检索到该内容需要高亮显示,必须配合检索(term、match)一起使用
  • 开始标签:如 <span style='color:red;'>,默认 <em>
  • 结束标签:如 </span>,默认 </em>
  • require_field_match:默认值为 true,表示只高亮匹配的字段

# 搜索排序

{
  "sort": [
    {field: {"order": 排序规则}},
    ...
  ]
}
1
2
3
4
5
6
  • 排序规则:asc 表示升序,desc 表示降序
  • 默认排序
    • Elasticsearch 按照查询和文档的相关度进行排序,默认按评分降序排序,即 "sort": [{"_score": "order": "asc"}]
    • 对于 match_all 查询而言,由于只返回所有文档,不需要评分,文档的顺序为添加文档的顺序,即 "sort": [{"_doc": "order": "asc"}]

# 聚合分析

# 指标聚合 (opens new window)

  • 从字段值计算指标(例如总和、平均值等)
{
  "size": 0,
  "aggs": {
    自定义统计字段名: {
      AGG_TYPE: {
        "field": 聚合字段
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
  • 指定 size=0,是为了不返回 hits 内容;aggs 也可以不使用缩写,即 aggregations
  • 聚合统计 AGG_TYPE:
    • value_count:统计包含指定字段的文档数量
    • min:统计最小值
    • max:统计最大值
    • avg:计算平均值
    • sum:计算总和
    • cardinality:统计基数,即去除重复项后的集合长度
    • stats:用于基本统计,会一次返回 count、min、max、avg 和 sum 这 5 个指标
    • grades_stats:用于高级统计,和基本统计功能类似,但是会比基本统计多 4 个统计结果:平方和(sum_of_squares)、方差(variance)、标准差(std_deviation)、平均值加/减两个标准差的区间(std_deviation_bounds)
    • percentiles:统计百分位

# 桶聚合 (opens new window)

  • 根据字段值、范围或其它条件将文档分组到桶(也称为箱)中,相当于 SQL 中的 group by
{
  "size": 0,
  "aggs": {
    自定义分组字段名: {
      "terms": { // 分组聚合
        "field": 分组字段,
        "order": {自定义统计字段: 排序规则},
        "size": 10 // 默认显示 10 组
      },
      "aggs": { // 分组后的统计查询,相当于 MySQL 分组函数查询
        自定义统计字段名: {
          AGG_TYPE: {
            "field": 聚合字段
          }
        }
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  • terms:分组聚合,根据某个字段对文档进行分组
  • filter:过滤器聚合,把符合过滤器中的条件的文档分到一个桶中
  • filters:多过滤器聚合,把符合多个过滤条件的文档分到不同的桶中
  • range:范围聚合, 用于反映数据的分布情况
  • ip_range:用于对 IP 类型数据范围聚合
  • date_range:用于日期类型的范围聚合
  • date_histogram:时间直方图聚合,常用于按照日期对文档进行统计
  • missing:空值聚合,可以把文档集中所有缺失字段的文档分到一个桶中
  • children:根据父子文档关系进行分桶
  • geo_distance:用于对地理点(geo_point)做范围统计

# 管道聚合 (opens new window)

  • 从其它聚合而不是文档或字段获取输入

# REST APIs

  • 共同选项
    • pretty=true:返回的 JSON 将被格式化
    • human=true:统计信息以适合人类可读的格式返回
    • filter_path=took,hits.hits._id,hits.hits._score:过滤返回,可以使用通配符 ***,或者使用 - 来排除一个或多个字段,如 filter_path=metadata.indices.*.state,-metadata.indices.logstash-*

# 集群管理

  • 脑裂:同一个集群中的不同节点对于集群的状态有了不一样的理解

  • 避免脑裂,修改配置 config/elasticsearch.yml

    1. discovery.zen.minimum_master_nodes,指定主节点选择过程中最少需要有多少个 master 节点,默认是 1,应设置为 N/2+1(N 是集群中的节点数),如在一个 3 节点的集群中,应设置为 2
    2. discovery.zen.ping.timeout,指定节点间网络通讯的等待时间,默认值是 3秒
  • 集群中的节点一般有 3 种角色

    • master 节点:负责创建索引、删除索引、追踪集群中节点的状态,以及跟踪哪些节点是群集的一部分,并决定将哪些分片分配给相关的节点等(只有候选主节点才有选举权和被选举权,其他节点不参与选举工作)
    • data 节点:负责数据的存储相关操作,如对数据进行增、删、改、查和聚合等
    • client 节点:起到路由请求,可以看作负载均衡器,适用于高并发访问的业务场景,配置 node.master: falsenode.data: false

# 运维相关命令 (opens new window)

# 集群

命令 说明
GET /_cat/health?v 查看集群的健康状态
GET /_cluster/health 查看集群的健康信息,查询参数:pretty=true 表示格式化输出;level=indices 显示索引状态;level=shards 显示分片信息
GET /_cluster/stats 查看集群的系统信息,包括 CPU、JVM 等信息
GET /_cluster/state 查看集群的详细信息,包括节点、分片等信息
GET /_cluster/pending_tasks 查看集群中堆积的任务
GET /_cluster/settings 查看集群设置

GET /_cluster/health 返回结果中各个属性的含义:

  • cluster_name:集群名称
  • status:集群的健康状态,green——所有主分片和从分片都可用,yellow——所有主分片可用,但存在不可用的从分片,red——存在不可用的主分片
  • timed_out:是否超时
  • number_of_nodes: 节点数,包括 master 节点和 data 节点
  • number_of_data_nodes: data 节点数
  • active_primaty_shards: 活动的主分片
  • active_shards:所有活动的分片数,包括主分片和副本
  • relocating_shards:正在发生迁移的分片
  • initializing_shards:正在初始化的分片
  • unassigned_shards:没有被分配的分片
  • delayed_unassigned_shards:延迟未被分配的分片
  • number_of_pending_tasks:master 节点任务队列中的任务数
  • number_of_in_flight_fetch:正在进行迁移的分片数量
  • task_max_waiting_in_queue_millis:队列中任务的最大等待时间
  • active_shards_percent_as_number:活动分片的百分比

# 节点

命令 说明
GET / 查看版本
GET /_cat/master?v 查看集群中 master 节点的信息(节点 ID、绑定的 IP 和节点名)
GET /_cat/nodes?v 查看集群中各个节点的当前状态,包括节点 CPU 使用率、 HeapMemory 使用率、负载情况等
GET /_cat/nodeattrs?v 查看单节点的自定义属性
GET /_nodes/stats?pretty=true 查看节点状态
GET /_nodes/process 查看节点的进程信息
GET /_nodes/hot_threads 查看高消耗的线程所执行的任务
GET /_nodes/<nodeip>/jvm,process,os 查看指定节点的 JVM、进程和操作系统信息
GET _cat/plugins?v 查看各节点的插件信息
GET /_cat/thread_pool?v 查看各节点的线程池统计信息,包括线程池的类型、活跃线程数、任务队列大小等

# 分片

命令 说明
GET /_cat/shards?v 查看集群中各分片的详细情况,包括索引名称、分片编号、是主分片还是副分片、分片的当前状态(对于分配失败的分片会有失败原因)、doc 数量、磁盘占用情况等;查看指定 index 的分片信息 GET _cat/shards/<index>?v
GET /_cat/allocation?v 查看集群中每个节点分片的分配数量以及它们所使用的硬盘空间大小
GET /_cat/recovery?v 查看集群中每个分片的恢复过程

# Segments

命令 说明
GET /_cat/segments?v 查看集群中各索引的 segment 信息,包括 segment 名称、所属 shard、内存或磁盘占用大小、是否刷盘等;查看指定 index 的 segment 信息:GET _cat/segment/<index>?v

# 索引模板

命令 说明
GET /_cat/templates?v 查看集群中所有模板

# 索引

命令 说明
GET /_cat/indices?v 查看集群中所有索引的详细信息,包括索引健康状态、索引开关状态、分片数、副本数、文档数量、标记为删除的文档数量、占用的存储空间等;查看指定索引的信息 GET _cat/indices/<index>?v
GET /_cat/aliases?v 查看集群中所有 aliases(索引别名)的信息,包括 aliases 对应的索引、路由配置等

# Mapping

命令 说明
GET /_mapping 查看集群中所有索引的 mapping
GET /<index>/_mapping 查看指定索引的 mapping

# 文档

命令 说明
GET /_cat/count?v 查看集群中的文档数量;查看指定 index 的文档数量 GET _cat/count/<index>?v
GET /<index>/_doc/<id> 查看文档中的数据

# 快照

命令 说明
GET _snapshot/_all 查看所有快照
GET _snapshot/<snapshot_name>/_status 查看指定快照的进度

# 插件

命令 说明
GET _cat/plugins?v 查看已安装的插件

# Java Clients

# Java Transport Client

  • org.elasticsearch.client:transport
  • TransportClient:传输机客户端,通过传输模块远程与 Elasticsearch 集群建立连接,但不会加入集群
  • 7.0 中已弃用

# Java API Client

# Java REST Client

  • Deprecated in 7.15.0. The Java REST Client is deprecated in favor of the Java API Client (opens new window).

  • Java Low Level REST Client

    • 低级别客户端,通过 HTTP 请求与 Elasticsearch 集群进行通信
    • API 本身不负责数据的编码解码,由用户去编码解码,可与所有的 Elasticsearch 版本兼容
    • RestClient 类是线程安全的,内部使用 Apache HTTP 异步客户端发送 HTTP 请求
    • Response performRequest(Request request):同步提交请求,会阻塞调用线程,并在请求成功时返回响应,或者在请求失败时引发异常
    • void performRequestAsync(Request request, ResponseListener responseListener):异步提交请求
    • 可在 Request 中添加请求构建选项 RequestOptions,可以多个 Request 共享同一个 RequestOptions
  • Java High Level REST Client (opens new window)

    • 高级客户端 RestHighLevelClient,基于低级别客户端来实现,主要目标是为了暴露各 API 特定的方法
    • 依赖于 Elasticsearch 核心项目 org.elasticsearch:elasticsearch,将 Request 对象作为参数,返回一个 Response 对象
    • 所有 API 都可以同步或异步调用:同步调用方法立即返回一个 Response 对象;异步调用方法(方法名以 async 结尾)依赖于监听,当有请求返回或是错误返回时,该监听会通知到对应的方法继续执行
    • 可以使用内置的帮助类 XContentFactory.jsonBuilder 来构建 JSON 文档
    • SearchRequest 的构建需要依赖 SearchSourceBuilder
    • The High Level Rest Client version 7.17 can work with Elasticsearch 8.x with compatibility mode enabled (opens new window).
SearchRequest searchRequest = new SearchRequest("index_name");

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.from(0).size(5);
searchSourceBuilder.sort(new FieldSortBuilder("id").order(SortOrder.ASC));

MatchAllQueryBuilder query = QueryBuilders.matchAllQuery();
searchSourceBuilder.query(query);

HighlightBuilder highlightBuilder = new HighlightBuilder();
HighlightBuilder.Field highlightTitle = new HighlightBuilder.Field("title");
highlightTitle.highlighterType("unified");
highlightBuilder.field(highlightTitle);
HighlightBuilder.Field highlightUser = new HighlightBuilder.Field("user");
highlightBuilder.field(highlightUser);
searchSourceBuilder.highlighter(highlightBuilder);

TermsAggregationBuilder aggregation = AggregationBuilders.terms("by_company").field("company.keyword")
        .subAggregation(AggregationBuilders.avg("average_age").field("age"));
searchSourceBuilder.aggregation(aggregation);

searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

SearchHits hits = searchResponse.getHits();

TotalHits totalHits = hits.getTotalHits();
SearchHit[] searchHits = hits.getHits();
for (SearchHit hit : searchHits) {
    // do something with the SearchHit
}

for (SearchHit hit : hits.getHits()) {
    Map<String, HighlightField> highlightFields = hit.getHighlightFields();
    HighlightField highlight = highlightFields.get("title");
    Text[] fragments = highlight.fragments();
    String fragmentString = fragments[0].string();
}

Aggregations aggregations = searchResponse.getAggregations();
Terms byCompanyAggregation = aggregations.get("by_company");
Terms.Bucket elasticBucket = byCompanyAggregation.getBucketByKey("Elastic");
Avg averageAge = elasticBucket.getAggregations().get("average_age");
double avg = averageAge.getValue();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

# Spring 操作 Elasticsearch (opens new window)

  • 实体注解
    • @Document,属性:indexName、shards、replicas
    • @Id
    • @Field 配置映射信息,如
      @Field(type = FieldType.Keyword)
      @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_max_word")
      @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss")
  • ElasticsearchRestTemplate、ReactiveElasticsearchTemplate:用于操作 Elasticsearch 的模板类
  • ElasticsearchRepository<T, ID>:用于完成常用操作的工具接口
  • NativeSearchQueryBuilder:用于生成查询条件的构造器,需要去封装各种查询条件,即 withXxx()addAggregation()
    • QueryBuilder:该接口表示一个查询条件,可以通过 QueryBuilders 工具类中的方法快速生成各种条件
      • termQuery():生成 term 条件,相当于 "term": { }
      • matchQuery():生成 match 条件,相当于 "match": { },如 QueryBuilder matchQuery = QueryBuilders.matchQuery("title", "Java 编程").operator(Operator.AND);,Operator.AND 表示使用 and 的方式连接被解析后的词项
      • rangeQuery():生成 range 条件,相当于 "range": { }
      • multiMatchQuery():生成关键字查询,相当于 "multi_match": { }
      • boolQuery():组合复杂查询,must()should()mustNot()filter()
    • Pageable:分页接口,可以通过 PageRequest.of(page, size) 获取,注意:page 从 0 开始
    • SortBuilder:排序构造器,可以通过 SortBuilders.fieldSort(field).order(order) 获取
    • HighlightBuilder:高亮显示构造器
    • AbstractAggregationBuilder:聚合查询构造器,可以通过 AggregationBuilders 工具类生成
    • SuggestBuilder:建议查询构造器,可以通过 SuggestBuilders 工具类生成
  • Query 接口,实现类:CriteriaQuery、StringQuery、NativeSearchQuery
    • void setMaxResults(Integer maxResults):设置返回结果的最大值
  • SearchHits,查询结果
  • Aggregations,聚合结果
  • Suggest,suggest 结果
IndexCoordinates indexCoordinates = IndexCoordinates.of("index_name");
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
        // 设置分页信息
        .withPageable(PageRequest.of(0, 2))
        // 设置排序信息
        .withSort(SortBuilders.fieldSort("_doc").order(SortOrder.DESC))
        // 设置查询条件
        .withQuery(QueryBuilders.xxxQuery)
        // 设置聚合查询
        .addAggregation(AggregationBuilders.xxx)
        .build();
searchQuery.setMaxResults(0);

SearchHits<SearchDocument> search = elasticsearchRestTemplate.search(searchQuery, SearchDocument.class, indexCoordinates);

long totalHits = search.getTotalHits();
for (SearchHit<SearchDocument> searchHit : search.getSearchHits()) {
    log.info(searchHit.getContent().toJson());
}

// 获取自定义的分组字段
ParsedStringTerms aggregations = search.getAggregations().get("xxx");
List<? extends Terms.Bucket> buckets = aggregations.getBuckets();
for (Terms.Bucket bucket : buckets) {

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 数据同步方案

ES同步方案

# Elastic Stack

Elastic Stack 的组成

Elasticsearch 作为实时分布式搜索和分析引擎,Kibana 实现灵活的可视化分析,Beats 从各个机器和系统采集数据,Logstash 采集、转换、优化和输出数据。

Updated at: 2023-09-28 18:13:06